package main

import (
	"context"
	"fmt"
	"github.com/go-sql-driver/mysql"
	"github.com/guonaihong/gout"
	"golang.org/x/crypto/ssh"
	sql "gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"math/rand"
	"net"
	"os"
	"strconv"
	"sync"
	"sync/atomic"
	"time"
)

type Dialer struct {
	client *ssh.Client
}

type SSH struct {
	Host     string `json:"host"`
	User     string `json:"user"`
	Port     int    `json:"port"`
	Type     string `json:"type"`
	Password string `json:"password"`
	KeyFile  string `json:"key"`
}

type MySQL struct {
	Host     string `json:"host"`
	User     string `json:"user"`
	Port     int    `json:"port"`
	Password string `json:"password"`
	Database string `json:"database"`
}

type Users struct {
	Id        int64  `gorm:"column:id"`
	Email     string `gorm:"column:email"`
	Name      string `gorm:"column:name"`
	Addr      int64  `gorm:"column:addr"`
	FirstName string `gorm:"column:fn"`
	LastName  string `gorm:"column:ln"`
}

func (v *Dialer) Dial(ctx context.Context, address string) (net.Conn, error) {
	return v.client.Dial("tcp", address)
}

func (s *SSH) DialWithPassword() (*ssh.Client, error) {
	address := fmt.Sprintf("%s:%d", s.Host, s.Port)
	config := &ssh.ClientConfig{
		User: s.User,
		Auth: []ssh.AuthMethod{
			ssh.Password(s.Password),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	return ssh.Dial("tcp", address, config)
}

func (s *SSH) DialWithKeyFile() (*ssh.Client, error) {
	address := fmt.Sprintf("%s:%d", s.Host, s.Port)
	config := &ssh.ClientConfig{
		User:            s.User,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
	if k, err := os.ReadFile(s.KeyFile); err != nil {
		return nil, err
	} else {
		signer, err := ssh.ParsePrivateKey(k)
		if err != nil {
			return nil, err
		}
		config.Auth = []ssh.AuthMethod{
			ssh.PublicKeys(signer),
		}
	}
	return ssh.Dial("tcp", address, config)
}

func (m *MySQL) New() (db *gorm.DB, err error) {
	// 填写注册的mysql网络
	dsn := fmt.Sprintf("%s:%s@mysql+ssh(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local",
		m.User, m.Password, m.Host, m.Port, m.Database)
	db, err = gorm.Open(sql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		return
	}
	return
}

const (
	NUMBER = 21
)

func main() {
	client := SSH{
		Host:     "52.34.30.100",
		User:     "centos",
		Password: "",
		Port:     22,
		KeyFile:  "E:/download/product.pem",
		Type:     "KEY", // PASSWORD or KEY
	}
	my := MySQL{
		Host:     "database-pro.cnnpa4mflrak.us-west-2.rds.amazonaws.com",
		User:     "test",
		Password: "Yujia543",
		Port:     3306,
		Database: "yj-question-test",
	}

	var (
		dial *ssh.Client
		err  error
	)
	switch client.Type {
	case "KEY":
		dial, err = client.DialWithKeyFile()
	case "PASSWORD":
		dial, err = client.DialWithPassword()
	default:
		panic("unknown ssh type!")
	}
	if err != nil {
		log.Fatalf("ssh connect error: %s", err.Error())
		return
	}
	defer dial.Close()

	// 注册ssh代理
	mysql.RegisterDialContext("mysql+ssh", (&Dialer{
		client: dial,
	}).Dial)

	db, err := my.New()
	if err != nil {
		log.Fatalf("mysql connect error: %s", err.Error())
		return
	}

	sqlDb, pErr := db.DB()
	if pErr != nil {
		panic("db error")
	}
	sqlDb.SetMaxIdleConns(10)
	sqlDb.SetMaxOpenConns(100)
	sqlDb.SetConnMaxLifetime(time.Hour)

	var users []*Users
	db.Raw("SELECT u.id, u.name, u.email, " +
		"(SELECT a.id FROM db_address a WHERE a.user_id = u.id LIMIT 1) AS addr, " +
		"(SELECT a.first_name FROM db_address a WHERE a.user_id = u.id LIMIT 1) AS fn, " +
		"(SELECT a.last_name FROM db_address a WHERE a.user_id = u.id LIMIT 1) AS `ln` " +
		"FROM db_user u WHERE u.email <> '' " +
		"AND u.status = 1 AND u.name <> '' " +
		"AND u.`password` <> '' " +
		"AND u.`password` = '0If3lS4jDjR8pdSfnp3ppQ==' " +
		"AND EXISTS(SELECT * FROM db_address d WHERE d.user_id = u.id) " +
		"AND (SELECT user_integral FROM db_exchange_records er WHERE er.user_id = u.id ORDER BY er.created_time DESC LIMIT 1) > 1 " +
		"ORDER BY u.created_time DESC LIMIT " +
		strconv.Itoa(rand.Intn(5)) + "0, " + strconv.Itoa(NUMBER)).
		Scan(&users)

	log.Println("users: ", users)

	token_chan := make(chan map[string]string, len(users))

	var twg sync.WaitGroup
	twg.Add(NUMBER)
	for i := 0; i < NUMBER; i++ {
		index := i
		go func() {
			defer func() {
				if err := recover(); err != nil {
					log.Println("get token goroutine error: ", err)
					twg.Done()
				}
			}()

			var rsp map[string]interface{}
			gout.POST("https://test1-question-api.boomyoka.com/question-api/user/v1/login").
				SetJSON(gout.H{
					"clientType": "H5",
					"email":      users[index].Email,
					"password":   "123456",
					"type":       "VERIFICATION_CODE",
				}).
				BindJSON(&rsp).
				Do()

			log.Println("rsp: ", rsp)
			token_info := (rsp["data"]).(map[string]interface{})

			token_chan <- map[string]string{
				"token":    token_info["token"].(string),
				"email":    users[index].Email,
				"addr":     strconv.FormatInt(users[index].Addr, 10),
				"fullName": users[index].FirstName + " " + users[index].LastName,
			}

			twg.Done()
		}()
	}

	twg.Wait()

	close(token_chan)

	length := len(token_chan)
	log.Println("token_chan length: ", length)

	var count int32 = 0

	var wg sync.WaitGroup
	wg.Add(length)
	for i := 0; i < length; i++ {
		go func() {
			defer func() {
				if err := recover(); err != nil {
					log.Println("order or exchange goroutine error: ", err)
					wg.Done()
				}
			}()

			token_info := <-token_chan
			log.Println("token_info: ", token_info)

			var rsp map[string]interface{}
			gout.POST("https://test1-question-api.boomyoka.com/question-api/box/v1/createdOrder").
				SetHeader(gout.H{
					"Authorization": token_info["token"],
				}).
				SetJSON(gout.H{
					"sourcePath":     2,
					"successUrl":     "https://test1-question-m.boomyoka.com/order/confirm?boxId=1630519528230494889",
					"failUrl":        "https://test1-question-m.boomyoka.com/order/confirm?boxId=1630519528230494889",
					"boxBuyTypeEnum": "PAY_CARD",
					"boxId":          "1630519528230494889",
					"giftId":         nil,
					"paymentType":    "ONEWAY_H5",
					"fullName":       token_info["fullName"],
					"spuUrl":         "https://test1-question-m.boomyoka.com/box/detail?propBoxId=1630519528230494889",
					"addressId":      token_info["addr"],
					"email":          token_info["email"],
					"avaEnabled":     true,
					"screenHeight":   864,
					"screenWidth":    1536,
					"osType":         "web",
					"num":            "1",
					"couponId":       "1678277299305869313",
				}).
				BindJSON(&rsp).
				Do()

			log.Println("order rsp: ", rsp)

			var result map[string]interface{}
			gout.POST("https://test1-question-api.boomyoka.com/question-api/coupon/code/exchange").
				SetHeader(gout.H{
					"Authorization": token_info["token"],
				}).
				SetJSON(gout.H{
					"giftId": "1678046663098507268",
					"point":  1,
				}).
				BindJSON(&result).
				Do()

			log.Println("exchange result: ", result)

			atomic.AddInt32(&count, 1)

			wg.Done()
		}()
	}

	wg.Wait()

	log.Println("count: ", count)
}
